package cz.worktracker.controller;

import cz.worktracker.enums.DataSetsEnum;
import cz.worktracker.model.entities.Customer;
import cz.worktracker.model.entities.Project;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.Map;
import java.util.logging.Level;
import javax.annotation.ManagedBean;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.enterprise.context.SessionScoped;
import javax.faces.application.FacesMessage;
import javax.faces.context.FacesContext;
import javax.faces.model.DataModel;
import javax.faces.model.ListDataModel;
import javax.inject.Inject;
import javax.inject.Named;
import org.hibernate.Criteria;
import org.hibernate.Query;
import org.hibernate.Transaction;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Restrictions;

/**
 * ProjectManager
 * Controller for working with JPA entity Project
 * @since 2010-04-23
 * @author kyjovsky
 */
@ManagedBean
@SessionScoped
@Named
public class ProjectManager extends AbstractManager
        implements Serializable {

    private static final long serialVersionUID = 1L;
    private static final String NAV_LIST = "/user/project/list";
    private static final String NAV_EDIT = "/user/project/edit";
    private static final String NAV_DETAIL = "/user/project/detail";
    private static final String NAV_REDIRECT = "?faces-redirect=true";
    private Project project;
    private AbstractPaginator paginator;
    private DataModel dataModel;
    private DataSetsEnum DSEnum;
    private int rowsPerPage = DSEnum.ITEMS_PER_PAGE_DEFAULT.getValue();
    private String searchPattern = "";
    private String sortByField = DSEnum.SORT_BY_FIELD.getStringValue();
    private String sortDirection = DSEnum.SORT_DIRECTION.getStringValue();
    private boolean nullFlag = true;
    @Inject
    UserManager userManager;
    @Inject
    CustomerManager customerManager;
    @Inject
    TaskManager taskManager;

    /**
     * Constructor
     */
    @PostConstruct
    public void construct() {
        init();
    }

    /**
     * Destructor
     */
    @PreDestroy
    public void destroy() {
        project = null;
        paginator = null;
        dataModel = null;
    }

    /**
     * This method initialize values
     */
    private void init() {
        project = new Project();
        project.setIdUser(-1);
        dataModel = null;
        paginator = null;
    }

    /**
    * This method reset current project
    * @return true
    */
    public boolean getNewProject() {
        if (isNullFlag()) {
            project = null;
            init();
        }

        setNullFlag(true);

        return true;
    }

    /**
     * This method create new project in db by current project
     * @return String navigation
     */
    public String create() {

        if (project != null) {
            try {
                project.setIdUser(userManager.getUser().getId());
                project.setIsActive("y");
                project.setCreate(new Date());
                if (project.getPricePerRateUnit() == null) {
                    project.setPricePerRateUnit(new Float(0.0));
                }
                Transaction tx = getSession().beginTransaction();
                getSession().save(project);
                tx.commit();
                init();
            } catch (Exception e) {
                getLogger(getClass()).log(Level.SEVERE, "Error on try to save Project: " + getProject(), e);
                addMessage("Error on try to save Project", FacesMessage.SEVERITY_ERROR);
                return null;
            }
        }

        return NAV_LIST;
    }

    /**
     * Method sets current project record for editation.
     * @return String navigation
     */
    public String edit() {
        project = (Project) getList().getRowData();

        return NAV_EDIT + NAV_REDIRECT;
    }

    /**
     * Method sets current project and navigate do new task form.
     * @return String navigation
     */
    public String newTask() {

        project = (Project) getList().getRowData();
        resetDataModel();
        taskManager.resetDataModel();
        return "/user/task/create" + NAV_REDIRECT;
    }


    /**
     * Method removes current project together with related content.
     * @return String
     */
    public void remove() {
        project = (Project) getList().getRowData();

        Transaction tx = getSession().beginTransaction();
        taskManager.removeByProjectId( project.getId() );
        getSession().delete( project );
        tx.commit();

        getProjectsCount();

        resetDataModel();
        resetPagination();

        // return NAV_LIST + NAV_REDIRECT;
    }


    /**
     * Method navigate to new project form
     * @return String navigation
     */
    public String newProjectTask() {

        resetDataModel();
        taskManager.resetDataModel();
        return "/user/task/create" + NAV_REDIRECT;
    }

    /**
     * Method set current project as actual selected project and navigate to project detail.
     * @return String navigation
     */
    public String detail() {

        project = (Project) getList().getRowData();
        resetDataModel();
        taskManager.resetDataModel();
        return NAV_DETAIL + NAV_REDIRECT;
    }

    /**
     * Method reset actual data model and navigate to show project form
     * @return String navigation
     */
    public String showList() {
        resetDataModel();
        searchPattern = "";
        return "/user/project/list" + NAV_REDIRECT;
    }

    /**
     * This method update in db current project.
     * @return String navigate
     */
    public String update() {
        if (project != null) {
            try {
                if (project.getPricePerRateUnit() == null) {
                    project.setPricePerRateUnit(new Float(0.0));
                }
                Transaction tx = getSession().beginTransaction();
                getSession().update(project);
                tx.commit();
                project = null;
                init();
            } catch (Exception e) {
                getLogger(getClass()).log(Level.SEVERE, "Error on try to save Project: " + getProject(), e);
                addMessage("Error on try to save Project", FacesMessage.SEVERITY_ERROR);
                return null;
            }
        }
        return NAV_LIST + NAV_REDIRECT;
    }

    /**
     * Prepares dataModel.
     * @return navigate.
     */
    public String prepareDataModel() {
        resetDataModel();
        return NAV_LIST + NAV_REDIRECT;
    }

    /**
     * Method nulls reference of dataModel.
     */
    public void resetDataModel() {
        dataModel = null;
    }

    /**
     * Method nulls reference of paginator.
     */
    public void resetPagination() {
        paginator = null;
    }

    /**
     * Returns page switcher and override abstract methods from AbstractPaginator.
     * @return Paginator object.
     */
    public AbstractPaginator getPaginator() {
        if (paginator == null) {
            paginator = new AbstractPaginator(rowsPerPage) {

                /*
                 * While creating a new instance we have to implement abstract
                 * methods defined in AbstractPaginator class.
                 */
                @Override
                public int getItemsCount() {
                    return getProjectsCount();
                }

                @Override
                public DataModel createPageDataModel() {
                    ListDataModel list = null;

                    try {
                        Transaction tx = getSession().beginTransaction();
                        Criteria ct = getSession().createCriteria(Project.class);
                        ct.add(Restrictions.eq("idUser", userManager.getUser().getId()));
                        //ct.add( Restrictions.eq( "isActive", "y" ) );

                        if( getSearchPattern().length() > 0 ) {
                            String pattern = "%" + getSearchPattern() + "%";
                            ct.add(Restrictions.disjunction().add(Restrictions.ilike("name", pattern)).add(Restrictions.ilike("note", pattern)));
                        }

                        if( getSortDirection().equalsIgnoreCase("asc") ) {
                            ct.addOrder(Order.asc(getSortByField()));
                        } else {
                            ct.addOrder(Order.desc(getSortByField()));
                        }

                        ct.setFirstResult(getPageFirstItem());
                        ct.setMaxResults(getPageSize());
                        list = new ListDataModel(ct.list());
                        tx.commit();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }

                    return list;
                }
            };
        }

        return paginator;
    }

    /**
     * Returns count of records returned to view.
     * @return int
     */
    public int getRowsPerPage() {
        return rowsPerPage;
    }

    /**
     * Sets count of records returned to view.
     * @param rowsPerPage
     */
    public void setRowsPerPage(int rowsPerPage) {
        this.rowsPerPage = rowsPerPage;

        resetDataModel();
        resetPagination();
    }

    /**
     * Returns DataModel for dataTable.
     * @return DataModel for dataTable.
     */
    public DataModel getList() {
        if (dataModel == null) {
            dataModel = getPaginator().createPageDataModel();
        }

        return dataModel;
    }

    /**
     * Returns DataModel for dataTable with recent records.
     * @return DataModel for dataTable.
     */
    public DataModel getListRecent() {
        dataModel = createPageDataModelRecent();
        return dataModel;
    }

    /**
     * Returns DataModel restricted by current customer id.
     * @return DataModel
     */
    public DataModel createPageDataModelCustomer() {
        ListDataModel list = null;

        try {
            Transaction tx = getSession().beginTransaction();
            Criteria ct = getSession().createCriteria(Project.class);
            ct.add(Restrictions.eq("idUser", userManager.getUser().getId()));
            ct.add(Restrictions.eq("idCustomer", customerManager.getCustomer().getId()));

            if (getSortDirection().equalsIgnoreCase("asc")) {
                ct.addOrder(Order.asc(getSortByField()));
            } else {
                ct.addOrder(Order.desc(getSortByField()));
            }

            ct.setFirstResult(getPaginator().getPageFirstItem());
            ct.setMaxResults(getPaginator().getPageSize());
            list = new ListDataModel(ct.list());
            tx.commit();
        } catch (Exception e) {
            e.printStackTrace();
        }

        return list;
    }

    /**
     * Returns DataModel restricted create date and maximum number of recent items.
     * @return DataModel
     */
    public DataModel createPageDataModelRecent() {
        ListDataModel list = null;

        try {
            Transaction tx = getSession().beginTransaction();
            Criteria ct = getSession().createCriteria(Project.class);
            ct.add(Restrictions.eq("idUser", userManager.getUser().getId()));
            ct.addOrder(Order.desc("create"));
            ct.setFirstResult(getPaginator().getPageFirstItem());
            ct.setMaxResults(DSEnum.ITEMS_MAX_RECENT_TIMERS.getValue());
            list = new ListDataModel(ct.list());
            tx.commit();
        } catch (Exception e) {
            e.printStackTrace();
        }

        return list;
    }

    /**
     * Returns DataModel for dataTable restricted by current customer.
     * @return DataModel for dataTable.
     */
    public DataModel getListCustomer() {
        dataModel = createPageDataModelCustomer();
        return dataModel;
    }

    /**
     * Next page.
     */
    public void next() {
        getPaginator().nextPage();
        resetDataModel();
    }

    /**
     * Last page.
     */
    public void last() {
        getPaginator().lastPage();
        resetDataModel();
    }

    /**
     * Previous page.
     */
    public void previous() {
        getPaginator().previousPage();
        resetDataModel();
    }

    /**
     * First page.
     */
    public void first() {
        getPaginator().firstPage();
        resetDataModel();
    }

    /**
     * Increments count of items per page.
     */
    public void more() {
        setRowsPerPage(getRowsPerPage() + DSEnum.ITEMS_PER_PAGE_INCREMENT.getValue());

        resetPagination();
        resetDataModel();
    }

    /**
     * * Decrements count of items per page.
     */
    public void less() {
        setRowsPerPage(getRowsPerPage() - DSEnum.ITEMS_PER_PAGE_INCREMENT.getValue());

        resetPagination();
        resetDataModel();
    }

    /**
     * Retrieves sort params through context and initiates sort.
     */
    public void sort() {
        /**
         * Try to gain params send by <f:param />
         */
        FacesContext context = FacesContext.getCurrentInstance();
        Map requestMap = context.getExternalContext().getRequestParameterMap();
        setSortByField((String) requestMap.get("by"));
        setSortDirection((String) requestMap.get("order"));

        resetPagination();
        resetDataModel();

    }

    /**
     * Returns projects of current customer count.
     * @return int means projects count.
     */
    public int getCustomerProjectCount() {
        Transaction tx = getSession().beginTransaction();
        String hql = "select count(*) from Project as project where project.idCustomer = '" + customerManager.getCustomer().getId() + "'";
        int count = ((Long) (getSession().createQuery(hql).iterate().next())).intValue();
        tx.commit();
        return count;
    }

    /**
     * Initiates searching.
     */
    public void search() {
        /**
         * Try to gain params send by <f:param />
         */
        //FacesContext context = FacesContext.getCurrentInstance();
        //Map requestMap = context.getExternalContext().getRequestParameterMap();
        //setSortByField( (String) requestMap.get("by") );
        resetPagination();
        resetDataModel();
    }

    /**
     * Cancels searching.
     */
    public void cancelSearch() {
        setSearchPattern("");

        resetPagination();
        resetDataModel();
    }

    /**
     * Returns projects of user count.
     * @return int means projects count.
     */
    public int getProjectsCount() {
        if( getSearchPattern().length() == 0 ) {
            Transaction tx = getSession().beginTransaction();
            String hql = "select count(*) from Project as project where project.idUser = '"
                    + userManager.getUser().getId() + "'";
            int num = ((Long) (getSession().createQuery(hql).iterate().next())).intValue();
            tx.commit();
            return num;
        } else {
            return getList().getRowCount();
        }
    }

    /**
     * Returns count of records in Data Model.
     * @return int
     */
    public int getDataModelRecordsCount() {

        if (getList() != null) {
            return getList().getRowCount();
        }

        return 0;
    }

    /**
     * Returns field name in JPA entity according which sorting will be provided.
     * @return String
     */
    public String getSortByField() {
        return sortByField;
    }

    /**
     * Sets field name in JPA entity according which sorting will be provided.
     * @param sortByField
     */
    public void setSortByField(String sortByField) {
        this.sortByField = sortByField;
    }

    /**
     * Returns sort direction (ascending: asc; descending desc).
     * @return String
     */
    public String getSortDirection() {
        return sortDirection;
    }

    /**
     * Sets sort direction (ascending: asc; descending desc).
     * @param sortDirection
     */
    public void setSortDirection(String sortDirection) {
        this.sortDirection = sortDirection;
    }

    /**
     * Returns searched string.
     * @return String
     */
    public String getSearchPattern() {
        return searchPattern;
    }

    /**
     * Sets searched string.
     * @param searchPattern
     */
    public void setSearchPattern(String searchPattern) {
        this.searchPattern = searchPattern;
    }

    /**
     * Method return current project
     * @return Project
     */
    public Project getProject() {
        return project;
    }

    /**
     * Method set current project
     * @param project
     */
    public void setProject(Project project) {
        this.project = project;

    }

    /**
     * Method change rateType in current project by customer rateType
     */
    public void changeRate() {
        if (project.getIdCustomer() == 0) {
            project.setPricePerRateUnit(new Float(0));
            project.setRateType("p");
        } else {
            try {
                Customer customer = getCustomerById(project.getIdCustomer());
                project.setPricePerRateUnit(customer.getPricePerRateUnit());
                project.setRateType(customer.getRateType());
                customer = null;
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * Method returns Customer by his id.
     * @param id Customer ID
     * @return Customer
     */
    public Customer getCustomerById(int id) {
        Transaction tx = getSession().beginTransaction();
        String hql = "from Customer as customer where customer.id = '" + id + "'";
        Query query = getSession().createQuery(hql);
        query.setMaxResults(1);
        Customer customer = (Customer) query.uniqueResult();
        tx.commit();
        return customer;
    }

    /**
     * Based on actual row, method returns Customer name
     * @return String
     */
    public String getRowCustomerName() {
        // Project tmpProject = (Project) getList().getRowData();
        Project tmpProject = (Project) dataModel.getRowData();
        int idCustomer = tmpProject.getIdCustomer();

        if (idCustomer > 0) {
            Customer tmpCustomer = getCustomerById(idCustomer);
            return tmpCustomer.getName();
        }

        return "--";
    }

    /**
     * Based on current project, method returns Customer name
     * @return String
     */
    public String getCustomerName() {
        // Project tmpProject = (Project) getList().getRowData();
        Project tmpProject = project;
        int idCustomer = tmpProject.getIdCustomer();

        if (idCustomer > 0) {
            Customer tmpCustomer = getCustomerById(idCustomer);
            return tmpCustomer.getName();
        }

        return "";
    }

    /**
     * Redirects to customerDetail view.
     * @return String
     */
    public String customerDetail() {
        Project tmpProject = (Project) dataModel.getRowData();
        int idCustomer = tmpProject.getIdCustomer();

        customerManager.setCustomer(getCustomerById(idCustomer));

        tmpProject = null;
        customerManager.resetDataModel();
        return "/user/customer/detail" + NAV_REDIRECT;
    }

    /**
     * Returns status of null flag.
     * @return
     */
    public boolean isNullFlag() {
        return nullFlag;
    }

    /**
     * Sets null flag.
     * @param nullFlag
     */
    public void setNullFlag(boolean nullFlag) {
        this.nullFlag = nullFlag;
    }

    /**
     * Returns id's projects of user.
     * @return ArrayList of Integer
     */
    public ArrayList<Integer> getProjectIds() {
        Transaction tx = getSession().beginTransaction();
        String hql = "select id from Project as project where project.idUser = '" + userManager.getUser().getId() + "'";
        ArrayList<Integer> result = new ArrayList<Integer>();
        Iterator it = getSession().createQuery(hql).iterate();

        while (it.hasNext()) {
            result.add((Integer) it.next());
        }

        tx.commit();

        return result;
    }

    /**
     * Return project from db by ID
     * @param id ID
     * @return Project
     */
    public Project getProjectById(int id) {
        Transaction tx = getSession().beginTransaction();
        String hql = "from Project as project where project.id = '" + id + "'";
        Query query = getSession().createQuery(hql);
        query.setMaxResults(1);
        project = (Project) query.uniqueResult();
        tx.commit();
        return project;
    }
}
